﻿using LightCAD.Core.Element3d;
using LightCAD.Three.OpenGL;
using System.Net;
using Constants = LightCAD.Runtime.Constants;

namespace QdArch
{
    public class DoorAction : ComponentInstance2dAction
    {
        private ElementInputer ElementInputer { get; set; }

        public DoorAction() { }
        public DoorAction(IDocumentEditor docEidtor) : base(docEidtor)
        {
             this.commandCtrl.WriteInfo("命令：Door");
        }

        public async void ExecCreate(string[] args = null)
        {
            //1. 选择元素
            //2. 选择门窗类型
            this.StartCreating();
            this.ElementInputer = new ElementInputer(this.docEditor);
        Step0:
            var result = await this.ElementInputer.Execute("选择墙体:");
            if (this.ElementInputer.isCancelled)
            {
                goto End;
            }
            if (result == null || result.ValueX == null || !(result.ValueX is QdWall))
            {
                goto Step0;
            }
        Step1:
            var instP = (Vector2)result.Extent;
            var wall = (QdWall)result.ValueX;
            var wallLine = wall.BaseLine;
            var isLineRight = IsRight(wallLine.Start, wallLine.End, instP);

            var doorDef = ComponentManager.GetCptDef<QdDoorDef>("门", "平开门", QdDoorAttribute.BuiltinUuid);

            double thickness = 50.0;
            double width = 900.0;
            double height = 2100.0;
            double bottom = 0;
            bool isLeft = false;
            bool isNear = isLineRight;
            var projectP = wallLine.Projection(instP);
            var door = new QdDoorInstance(thickness, width, height, bottom, isLeft, isNear, 90, true, doorDef);
            door.Initilize(this.docRt.Document);
            door.Position.Set(projectP.X, projectP.Y, 0);
            door.Wall = wall;
            door.Rotation.Z = wallLine.Dir.Angle();

            wall.OnPropertyChangedBefore(nameof(wall.AssociateElements), wall.AssociateElements, wall.AssociateElements);
            wall.AddLcComponent(door, nameof(QdDoorInstance), AssociateType.Cross);
            wall.OnPropertyChangedAfter(nameof(wall.AssociateElements), wall.AssociateElements, wall.AssociateElements);
            this.DrawDoor(door);
        End:
            this.ElementInputer = null;
            this.EndCreating();
        }

        public override void Draw(SKCanvas canvas, LcElement element, Vector2 offset)
        {
            var door = element as QdDoorInstance;

            var curves = door.Curves[0].Curve2ds;
            var pen = this.GetDrawPen(element);
            if (pen == Constants.defaultPen)
            {
                //pen = new SKPaint { Color = new SKColor(element.GetColorValue()), IsStroke = true };
                pen = new SKPaint { Color = SKColors.Red, IsStroke = true };
            }

            var box2d = door.BoundingBox;
            var rectPen = new SKPaint { Color = SKColors.Green, IsStroke = true };

            var lt = this.vportRt.ConvertWcsToScr(box2d.LeftTop).ToSKPoint();
            var rt = this.vportRt.ConvertWcsToScr(box2d.RightTop).ToSKPoint();
            var lb = this.vportRt.ConvertWcsToScr(box2d.LeftBottom).ToSKPoint();
            var rb = this.vportRt.ConvertWcsToScr(box2d.RightBottom).ToSKPoint();
            //canvas.DrawLine(lt, rt, rectPen);
            //canvas.DrawLine(rt, rb, rectPen);
            //canvas.DrawLine(rb, lb, rectPen);
            //canvas.DrawLine(lb, lt, rectPen);
            DrawDoorCurves(canvas, curves, pen);
        }

        public void DrawDoorCurves(SKCanvas canvas, List<Curve2d> curves,SKPaint pen)
        {

            foreach (var curve in curves)
            {
                switch (curve.Type)
                {
                    case Curve2dType.Line2d:
                        {
                            var line = curve as Line2d;
                            var wsp = line.Start;
                            var wep = line.End;

                            var ssp = this.vportRt.ConvertWcsToScr(wsp).ToSKPoint();
                            var sep = this.vportRt.ConvertWcsToScr(wep).ToSKPoint();
                            canvas.DrawLine(ssp, sep, pen);
                            break;
                        }
                    case Curve2dType.Arc2d:
                        {
                            var arc = curve as Arc2d;

                            var wcenter = arc.Center;
                            var scenter = this.vportRt.ConvertWcsToScr(wcenter).ToSKPoint();

                            var wrect = new Box2(wcenter.X - arc.Radius, wcenter.Y - arc.Radius, wcenter.X + arc.Radius , wcenter.Y + arc.Radius );
                            var plt = this.vportRt.ConvertWcsToScr(wrect.LeftTop);
                            var prb = this.vportRt.ConvertWcsToScr(wrect.RightBottom);
                            var scrRect = new SKRect((float)plt.X, (float)plt.Y, (float)prb.X, (float)prb.Y);
                            double startAngle = Utils.RadianToDegree(arc.StartAngle); //绘制弧线使用的是角度不是弧度
                            double endAngle = Utils.RadianToDegree(arc.EndAngle); //绘制弧线使用的是角度不是弧度
                            var sweep = endAngle - startAngle;
                            //Skia绘制圆弧是按照顺时针的，与数学逻辑相反
                            canvas.DrawArc(scrRect, -(float)(Utils.RadianToDegree(arc.EndAngle)), (float)sweep, false, pen);
                            break;
                        }
                    case Curve2dType.Polyline2d:
                        {
                            var pline = curve as Polyline2d;
                            var scrPoints = pline.Points.Select((p) => p.ToSKPoint()).ToArray();
                            canvas.DrawPoints(SKPointMode.Polygon, scrPoints, pen);
                            break;
                        }
                    case Curve2dType.Polygon2d:
                        {
                            var polygon = curve as Polygon2d;
                            var scrPoints = polygon.Points.Select((p) => p.ToSKPoint()).ToList();
                            scrPoints.Add(scrPoints[0]);
                            canvas.DrawPoints(SKPointMode.Polygon, scrPoints.ToArray(), pen);
                            break;
                        }
                    default:
                        break;
                }
            }
        }


        public void DrawDoor(QdDoorInstance doorRef)
        {
            this.docRt.Document.ModelSpace.InsertElement(doorRef);
            this.docRt.Action.ClearSelects();
        }

        #region Grip
        private QdDoorInstance _door;
        private string _gripName;
        private Vector2 _position;

        public override ControlGrip[] GetControlGrips(LcElement element)
        {
            var doorRef = element as QdDoorInstance;
            var grips = new List<ControlGrip>();

            var gripCenter = new ControlGrip
            {
                Element = doorRef,
                Name = "Center",
                Position = doorRef.Position.ToVector2()
            };
            grips.Add(gripCenter);

            var wallLine = doorRef.Wall.BaseLine;
            var gripLeft = new ControlGrip
            {
                Element = doorRef,
                Name = "Left",
                Position = doorRef.StartPoint
            };
            grips.Add(gripLeft);

            var gripRight = new ControlGrip
            {
                Element = doorRef,
                Name = "Right",
                Position = doorRef.EndPoint
            };
            grips.Add(gripRight);

            
            var gripOpenDir = new ControlGrip
            {
                Element = doorRef,
                Name = "Control",
                Position = doorRef.ControlPoint
            };
            grips.Add(gripOpenDir);

            return grips.ToArray();
        }

        public override void SetDragGrip(LcElement element, string gripName, Vector2 position, bool isEnd)
        {
            var door = element as QdDoorInstance;
            _door = door;
            if (!isEnd)
            {
                _gripName = gripName;
                _position = position;
            }
            else
            {
                if (gripName == "Center")
                {
                    door.OnPropertyChangedBefore("Position", null, null);
                    position = door.Wall.BaseLine.Projection(position);
                    var offset = position - door.Position.ToVector2();
                    door.Position.Set(position.X, position.Y, 0);
                    door.ResetCache();

                    door.Wall.OnPropertyChangedBefore(nameof(door.Wall.AssociateElements), door.Wall.AssociateElements, door.Wall.AssociateElements);
                    door.Wall.UpdateLcComponent(door, nameof(QdDoorInstance), AssociateType.Cross);
                    door.Wall.OnPropertyChangedAfter(nameof(door.Wall.AssociateElements), door.Wall.AssociateElements, door.Wall.AssociateElements);
                    door.OnPropertyChangedAfter("Position", null, null);

                }
                else if(gripName == "Right")
                {
                    door.OnPropertyChangedBefore("Position", null, null);
                    position = door.Wall.BaseLine.Projection(_position);
                    var offset = (_position - door.EndPoint) / 2;

                    door.Position.Add(offset.X, offset.Y, 0);

                    door.Width = Vector2.Distance(door.EndPoint, door.StartPoint);
                    door.EndPoint.Set(_position.X, _position.Y);
                    door.Width = Vector2.Distance(door.EndPoint, door.Position.ToVector2()) * 2;
                    door.ResetCache();

                    door.Wall.OnPropertyChangedBefore(nameof(door.Wall.AssociateElements), door.Wall.AssociateElements, door.Wall.AssociateElements);
                    door.Wall.UpdateLcComponent(door, nameof(QdDoorInstance), AssociateType.Cross);
                    door.Wall.OnPropertyChangedAfter(nameof(door.Wall.AssociateElements), door.Wall.AssociateElements, door.Wall.AssociateElements);
                    door.OnPropertyChangedAfter("Position", null, null);
                }
                else if (gripName == "Left")
                {
                    door.OnPropertyChangedBefore("Position", null, null);
                    position = door.Wall.BaseLine.Projection(_position);
                    var offset = (_position - door.StartPoint) / 2;

                    door.Position.Add(offset.X, offset.Y, 0);

                    door.Width = Vector2.Distance(door.EndPoint, door.StartPoint);
                    door.StartPoint.Set(_position.X, _position.Y);
                    door.Width = Vector2.Distance(door.StartPoint, door.Position.ToVector2()) * 2;
                    door.ResetCache();

                    door.Wall.OnPropertyChangedBefore(nameof(door.Wall.AssociateElements), door.Wall.AssociateElements, door.Wall.AssociateElements);
                    door.Wall.UpdateLcComponent(door, nameof(QdDoorInstance), AssociateType.Cross);
                    door.Wall.OnPropertyChangedAfter(nameof(door.Wall.AssociateElements), door.Wall.AssociateElements, door.Wall.AssociateElements);
                    door.OnPropertyChangedAfter("Position", null, null);
                }
                else if (_gripName == "Control")
                {
                    bool needCurveReset = false;
                    door.OnPropertyChangedBefore("ControlPoint", null, null);
                    var wallLine = door.Wall.BaseLine;
                    var isNear = IsRight(wallLine.Start, wallLine.End, _position);
                    if (door.IsNear != isNear)
                    {
                        door.IsNear = isNear;
                        var mirrorMatrix = Matrix3.GetMirror(wallLine.Start, wallLine.End);
                        var controlP = mirrorMatrix.MultiplyPoint(door.ControlPoint);
                        needCurveReset = true;
                    }

                    var dir = wallLine.Dir;
                    var rotateDir = Vector2.RotateInRadian(dir, Math.PI / 2);
                    var newInsertP = door.Position.ToVector2() + (rotateDir * door.Width);
                    var isLeft = !IsRight(door.Position.ToVector2(), newInsertP, _position);
                    if (door.IsLeft != isLeft)
                    {
                        door.IsLeft = isLeft;
                        var mirrorMatrix = Matrix3.GetMirror(door.Position.ToVector2(), newInsertP);
                        var controlP = mirrorMatrix.MultiplyPoint(door.ControlPoint);
                        needCurveReset = true;
                    }
                    if (needCurveReset)
                    {
                        door.ResetCache();
                    }
                    door.OnPropertyChangedAfter("ControlPoint", null, null);
                }
            }
        }

        public override void DrawDragGrip(SKCanvas canvas)
        {
            if (_door == null)
                return;

            var door = new QdDoorInstance(_door.DoorDef);
            door.Copy((QdDoorInstance)_door);
            var wallLine = door.Wall.BaseLine;
            if (_gripName == "Center")
            {
                _position = door.Wall.BaseLine.Projection(_position);
                //var offset = _position - door.Position.ToVector2();
                door.Position.Set(_position.X, _position.Y, 0);
            }
            else if (_gripName == "Right")
            {
                _position = door.Wall.BaseLine.Projection(_position);
                var offset = (_position - door.EndPoint) / 2;

                door.Position.Add(offset.X, offset.Y, 0);
                door.EndPoint.Set(_position.X, _position.Y);
                door.Width = Vector2.Distance(door.EndPoint, door.Position.ToVector2()) * 2;
                door.ResetCache();
            }
            else if (_gripName == "Left")
            {
                _position = door.Wall.BaseLine.Projection(_position);
                var offset = (_position - door.StartPoint) / 2;

                door.Position.Add(offset.X, offset.Y, 0);
                door.StartPoint.Set(_position.X, _position.Y);
                door.Width = Vector2.Distance(door.StartPoint, door.Position.ToVector2()) * 2;
                door.ResetCache();
            }
            else if(_gripName == "Control")
            {
                bool needReset = false;
                var isNear = IsRight(wallLine.Start, wallLine.End, _position);
                if (door.IsNear != isNear)
                {
                    door.IsNear = isNear;
                    var mirrorMatrix = Matrix3.GetMirror(wallLine.Start, wallLine.End);
                    var controlP = mirrorMatrix.MultiplyPoint(door.ControlPoint);
                    needReset = true;
                }

                var dir = wallLine.Dir;
                var rotateDir = Vector2.RotateInRadian(dir, Math.PI / 2);
                var newInsertP = door.Position.ToVector2() + (rotateDir * door.Width);

                var isLeft = !IsRight(door.Position.ToVector2(), newInsertP, _position);
                if (door.IsLeft != isLeft)
                {
                    door.IsLeft = isLeft;
                    var mirrorMatrix = Matrix3.GetMirror(door.Position.ToVector2(), newInsertP);
                    var controlP = mirrorMatrix.MultiplyPoint(door.ControlPoint);
                    needReset = true;
                }
                if (needReset)
                {
                    door.ResetCache();
                }
            }
            var curves = door.Curves[0].Curve2ds;
            DrawDoorCurves(canvas, curves, Constants.draggingPen);
        }
        #endregion

        public bool IsRight(Vector2 start, Vector2 end, Vector2 p)
        {
            //Vsub(D,A,B) D.x=A.x-B.x; D.y=A.y-B.y
            Vector2 v1 = start - end;
            Vector2 v2 = p - start;

            double f = v1.X * v2.Y - v2.X * v1.Y;
            return f >= 0;
        }

        public override SnapPointResult SnapPoint(SnapRuntime snapRt, LcElement element, Vector2 point, bool forRef, Vector2 PrePoint = null)
        {

            var maxDistance = vportRt.GetSnapMaxDistance();
            QdDoorInstance door = element as QdDoorInstance;
            var sscur = SnapSettings.Current;
            var result = new SnapPointResult { Element = element };
            Curve2d line2d =new Line2d(door.StartPoint,door.EndPoint);
            var line = new LcLine(door.StartPoint, door.EndPoint);
            if (sscur.ObjectOn)
            {

                if (sscur.PointType.Has(SnapPointType.Endpoint))
                {
                    if ((point - line.Start).Length() <= maxDistance)
                    {
                        result.Point = line.Start;
                        result.Name = "Start";
                        result.Curves.Add(new SnapRefCurve(SnapPointType.Endpoint, line2d));
                    }
                    else if ((point - line.End).Length() <= maxDistance)
                    {
                        result.Point = line.End;
                        result.Name = "End";
                        result.Curves.Add(new SnapRefCurve(SnapPointType.Endpoint, line2d));
                    }
                }
                if (sscur.PointType.Has(SnapPointType.Midpoint) && result.Point == null)
                {
                    var cp = (line.Start + line.End) / 2;
                    if ((point - cp).Length() <= maxDistance)
                    {
                        result.Point = cp;
                        result.Name = "Mid";
                        result.Curves.Add(new SnapRefCurve(SnapPointType.Midpoint, line2d));
                    }
                }
                //forRef时只捕捉端点，中点等，如果
                if (!forRef && result.Point == null)
                {
                    var refPoints = snapRt.GetRefPoints(element);
                    var lineLength = (line.End - line.Start).Length();
                    var distance = GeoUtils.PointToLineDistance(line.Start, line.End, point, out Vector2 nearest, out double dirDistance);
                    if (sscur.PointType.Has(SnapPointType.Nearest))
                    {
                        //最近点必须在线段内进行测试
                        if (distance < maxDistance && dirDistance > 0 && dirDistance < lineLength)
                        {
                            result.Point = nearest;
                            result.Name = "Nearest";
                            result.Curves.Add(new SnapRefCurve(SnapPointType.Nearest, line2d));
                        }
                    }
                    //进行延长线上点捕捉，需要元素上有参考点
                    if (sscur.PointType.Has(SnapPointType.ExtensionLine) && refPoints.Count > 0 && result.Point == null)
                    {
                        if (distance < maxDistance)
                        {
                            result.Point = nearest;
                            result.Name = "Extension";
                            result.Curves.Add(new SnapRefCurve(SnapPointType.ExtensionLine, line2d));
                        }
                    }
                }
            }

            if (result.Point != null)
                return result;
            else
                return null;
        }

        public override List<PropertyObserver> GetPropertyObservers()
        {
            return new List<PropertyObserver>() {
             new PropertyObserver()
            {
                Name = "Width",
                DisplayName = "宽度",
                CategoryName = "Geometry",
                CategoryDisplayName = "几何图形",
                PropType=PropertyType.Double,
                Getter = (ele) => (ele as QdDoorInstance).Parameters.GetValue<double>("Width"),
                Setter = (ele, value) =>
                {
                    var door = (ele as QdDoorInstance);
                    if (!double.TryParse(value.ToString(),out var width)||width<0)
                        return;
                    //door.Wall.OnPropertyChangedBefore("Door",null,null);
                    door.OnPropertyChangedBefore("Width",door.Parameters.GetValue<double>("Width"),width);
                    door.Width=width;
                    var wallLine = door.Wall.BaseLine;
                    var wallLineDir = wallLine.Dir;
                    var offset = wallLineDir * (door.Width / 2);
                    door.ResetCache();
                    door.Wall.UpdateLcComponent(door,nameof(QdDoorInstance),AssociateType.Cross);
                    door.OnPropertyChangedAfter("Width",door.Parameters.GetValue<double>("Width"),width);
                    //door.Wall.OnPropertyChangedAfter("Door",null,null);

                }
            },
              new PropertyObserver()
            {
                Name = "Height",
                DisplayName = "高度",
                CategoryName = "Geometry",
                CategoryDisplayName = "几何图形",
                PropType=PropertyType.Double,
                Getter = (ele) => (ele as QdDoorInstance).Parameters.GetValue<double>("Height"),
                Setter = (ele, value) =>
                {
                    var door = (ele as QdDoorInstance);
                    if (!double.TryParse(value.ToString(),out var height)||height<0)
                        return;
                    door.OnPropertyChangedBefore("Height",door.Parameters.GetValue<double>("Height"),height);
                    door.Parameters.SetValue("Height",height );
                    door.ResetCache();
                    door.Wall.UpdateLcComponent(door,nameof(QdDoorInstance),AssociateType.Cross);
                    door.OnPropertyChangedAfter("Height",door.Parameters.GetValue<double>("Height"),height);
                }
            },
              new PropertyObserver()
            {
                Name = "Bottom",
                DisplayName = "底高度",
                CategoryName = "Geometry",
                CategoryDisplayName = "几何图形",
                PropType=PropertyType.Double,
                Getter = (ele) => (ele as QdDoorInstance).Parameters.GetValue<double>("Bottom"),
                Setter = (ele, value) =>
                {
                    var door = (ele as QdDoorInstance);
                    if (!double.TryParse(value.ToString(),out var bottom)||bottom<0)
                        return;
                    door.OnPropertyChangedBefore("Bottom",door.Parameters.GetValue<double>("Bottom"),bottom);
                    door.Parameters.SetValue("Bottom",bottom );
                    door.ResetCache();
                    door.Wall.UpdateLcComponent(door,nameof(QdDoorInstance),AssociateType.Cross);
                    door.OnPropertyChangedAfter("Bottom",door.Parameters.GetValue<double>("Bottom"),bottom);

                }
            }
            };
        }
    }
}
